#include "kern_ll.h"
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "comm_arbiter.h"
#include "comm_mem.h"
#include "comm_slab.h"
#include <time.h>


#include <sched.h>
#include <sys/types.h>
#include <errno.h>
//#include <linux/unistd.h>

/*
 * initialize the communication arbiter
 */
unsigned int init_comm_arbiter(unsigned int size)
{
	// initialize mem_sem so only one thread can access it at a time
	#ifdef DEBUG
		printf("initializing variables\n");
	#endif
	
	set_slab_size = size;
	reads = 0;
		
	allocated_regions = reg_allocation_queue = high_allocation_queue = NULL;
	pthread_mutex_init(&queue_mutex, NULL);
	pthread_mutex_init(&work_mutex, NULL);
	pthread_mutex_init(&mem_mutex, NULL);

	// initialize the condition variable
	
	#ifdef DEBUG
		printf("initializing conditionals, mutexes, and semaphores \n");
	#endif
	pthread_condattr_init(&queue_wait_attr);
	pthread_cond_init(&queue_wait,&queue_wait_attr);
	sem_init(&request_sem,PTHREAD_PROCESS_PRIVATE,0);	
	
	pthread_attr_t attr; 
	pthread_attr_init(&attr);
	pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

	
	
	#ifdef DEBUG
		printf("initializing and running main arbiter thread \n");
	#endif
	com_id = 0;
	pthread_create( &comm_arbiter_thread, &attr, &comm_arbiter_main, NULL);
	#ifdef DEBUG
		printf("waiting on com_id ... ");
	#endif
	while(com_id == 0);
	#ifdef DEBUG
		printf("received %i \n",com_id);
	#endif
	
	return com_id;
}

PLINKEDLIST arbitrate_queue(PLINKEDLIST list, unsigned char type)
{
	#ifdef DEBUG
		printf(" -- arbitrate_queue(%X,%i)\n",list,type);
	#endif
	
	PMEMREQUEST request = NULL;
	
	while(list != NULL)
	{
		// get the request at the front of the list
		request = LIST_ENTRY_PTR(list,MEM_REQUEST,queue_list);
		// assign the associated mem region so the signaled thread can use it
		
		request->associated_mem_region = allocate_queue_entry(request->fd,request->offset,request->size);
	
		// remove the entry from the list to indicate it is finished
		KernUnlinkListEntry(&reg_allocation_queue,&(request->queue_list));
		
		// should split this to another conditional
		if (type == NOWAIT)
		{
			#ifdef DEBUG
				printf("Signaling waiting threads 1\n");
			#endif
			// signal waiting thread(s)
			//pthread_cond_signal(&queue_wait);
			
			//pthread_cond_broadcast(&queue_wait);
		}
		if (list == NULL)
		{
			break;	
		}
		
		list = list->nextPtr;
		
	}
	
	// signal waiting thread(s)
	
	if (type == WAIT)
	{
		#ifdef DEBUG
				printf("Signaling waiting threads 2\n");
			#endif
		//pthread_cond_signal(&queue_wait);
		pthread_cond_broadcast(&queue_wait);
		
	}
	return list;
}


/*
 * comm_arbiter_main is the main communication arbiter function
 */
void *comm_arbiter_main()
{
	#ifdef DEBUG
		printf("entered comm_arbiter_main\n");
	#endif
	com_id = pthread_self();
	struct timespec timer;
	timer.tv_sec = 2;
	
	unsigned long mask = 1;

	while(1)
	{
		
		pthread_mutex_lock( &queue_mutex );

		if (reg_allocation_queue == NULL)
		{
//			printf("I'm gon wait\n");
			pthread_cond_wait( &arbiter_wait, &queue_mutex );
//			printf("oh, I've been awaken\n");
		}
		
		#ifdef USE_SLAB
			reg_allocation_queue=arbitrate_slab_queue(reg_allocation_queue,WAIT);
		#else
			reg_allocation_queue=arbitrate_queue(reg_allocation_queue,WAIT);
		#endif
		
		pthread_mutex_unlock( &queue_mutex );
		//we've been awaken from our slumber, what the wtf
		//printf("waiting in thread arbiter main thread\n");
	}
}

/*
 * within_previous_region identifies memory that has already been al
 */
PMEMREGION within_previous_region(unsigned int fd, u_int64_t offset, u_int64_t size)
{
	#ifdef DEBUG
		printf(" -- within_previous_region(%i,%i,%i)\n",fd,offset,size);
	#endif
	PLINKEDLIST ptr = allocated_regions;
	PMEMREGION mem_region=NULL;
	while( ptr != NULL )
	{
			mem_region = LIST_ENTRY_PTR(ptr,MEM_REGION,list);
			if (offset >= mem_region->offset && offset < (mem_region->offset+mem_region->size_allocated))
			{
				// offset is within this memory region 
				return mem_region;
			}
			ptr = ptr->nextPtr;
	}
	
	return NULL;
}


/*
 * queue_and_wait queues request for memory
 */
PMEMREGION queue_and_wait(unsigned int fd, u_int64_t offset, u_int64_t size, unsigned char read_priority)
{
	#ifdef DEBUG
		printf(" -- queue_and_wait(%i,%i,%i,%i)\n",fd,offset,size,read_priority);
	#endif
	PLINKEDLIST *ptr_list = NULL;
	char signal =0;
	PMEMREGION new_mem_region=NULL;
	switch(read_priority)
	{
		case HIGH_PRIORITY:
			ptr_list = &high_allocation_queue;
			break;
		default:
			ptr_list = &reg_allocation_queue;
	}	
	#ifdef DEBUG
		printf("ptr_list points to list\n");
	#endif
	// atomically add to the queue
	PMEMREQUEST mem_region=NULL,upper_region=NULL;
	PLINKEDLIST ptr = reg_allocation_queue;
    
	#ifdef DEBUG
		printf("Creating mem request\n");
	#endif
	PMEMREQUEST new_request = (PMEMREQUEST)malloc(sizeof(MEM_REQUEST));
	new_request->fd = fd;
	new_request->offset = offset;
	new_request->size = size;
	new_request->associated_mem_region = NULL;
	pthread_mutex_lock( &queue_mutex );
	#ifdef DEBUG
		printf("Adding entry to queue\n");
	#endif
	if (*ptr_list == NULL)
	{
		KernAddListEntry(&reg_allocation_queue,&(new_request->queue_list));
		reg_allocation_queue_tail = reg_allocation_queue;
	}
	else
	{
		KernAddListEntryAfter(&reg_allocation_queue,reg_allocation_queue_tail,&(new_request->queue_list));
		reg_allocation_queue_tail = reg_allocation_queue_tail->nextPtr;
	}
	// wait for the condition variabe to be set
	
	struct timespec timer;
	timer.tv_sec = 2;
	//timer.tv_nsec = 2000000;
	while( new_request->associated_mem_region == NULL)
	{
		pthread_cond_signal(&arbiter_wait);
//		printf("going to wait for conditional\n");
		pthread_cond_wait( &queue_wait, &queue_mutex );
		
		//pthread_cond_timedwait( &queue_wait, &queue_mutex,&timer);
	//	printf("was mem region allocated? %i %i\n",offset,size);
		//signal++;
	}
	pthread_mutex_unlock( &queue_mutex );
	PMEMREGION associated_mem_region = new_request->associated_mem_region;
	free(new_request);
	return associated_mem_region;
}

/*
 * allocate_queue_entry allocates and queues a given entry
 */

PMEMREGION allocate_queue_entry(unsigned int fd, u_int64_t offset, u_int64_t size)
{
	#ifdef DEBUG
		printf(" -- allocate_queue_entry(%i,%i,%i)\n",fd,offset,size);
	#endif
	
	PMEMREGION new_mem_region = NULL;
	
	//if ( (new_mem_region == within_previous_region(fd,offset,size))  != NULL)
//		return new_mem_region;
	new_mem_region = (PMEMREGION)malloc(sizeof(MEM_REGION));
	PLINKEDLIST ptr = allocated_regions;
	PMEMREGION mem_region=NULL,upper_region=NULL;
	// grow to read ahead
	#ifdef DEBUG
		printf("Size request is %i, will be %i\n",size,READ_AHEAD_REGION(size));
	#endif
	size = READ_AHEAD_REGION(size);
	
	
	while( ptr != NULL )
	{
			mem_region = LIST_ENTRY_PTR(ptr,MEM_REGION,list);
			if (offset >= (mem_region->offset+mem_region->size_allocated))
			{
				 if (ptr->nextPtr!=NULL)
				 {
				 	upper_region = LIST_ENTRY_PTR(&(ptr->nextPtr),MEM_REGION,list);
				 	if ((offset+size) <= upper_region->offset)
				 	{
				 		ptr=ptr->nextPtr;
				 		
				 		break;
					}
				 }
			}
			if (ptr->nextPtr == NULL)
				break;
			ptr = ptr->nextPtr;
	}
	
	pthread_mutex_lock( &mem_mutex );
	//ptr contains the location in which we can 
	//KernAddListEntryAfter(list,after,entry)
	#ifdef DEBUG
		printf("Adding mem region to allocated regions\n");
	#endif
	if (allocated_regions == NULL)
		KernAddListEntry(&allocated_regions,&(new_mem_region->list));
	else
		KernAddListEntryAfter(&allocated_regions,ptr,&(new_mem_region->list));
	pthread_mutex_unlock( &mem_mutex );
	// get from fd
	//TODO ONCE EVERYTHING IS COMPILED
	new_mem_region->offset = offset;
	 lseek(fd,offset,SEEK_SET);
	//read(fd,new_mem_region->ptr,size);
	new_mem_region->ptr = malloc(size);
//
	//printf("****performing read****\n");
	new_mem_region->size_allocated = read(fd,new_mem_region->ptr,size);
	reads++;
	#ifdef DEBUG
		
	#endif
	//printf("Requested %i, got %i\n",size,new_mem_region->size_allocated);
	
	return new_mem_region;
}


/*
 * allocate_img_region queues memory
 */
PMEMREGION allocate_img_region(unsigned int fd, u_int64_t offset, u_int64_t size, unsigned char read_priority)
{
	#ifdef DEBUG
		printf(" -- allocate_img_region(%i,%i,%i,%i)\n",fd,offset,size,read_priority);
	#endif
	return queue_and_wait(fd,offset,size,read_priority);
}

/*
 * append_contiguous_region appends contiguous regions of memory together
 */
u_int64_t append_contiguous_region(int fd,PMEMREGION ptr,u_int64_t offset, u_int64_t size, char *buf,u_int64_t actual_size)
{
	#ifdef DEBUG
		printf(" -- append_contiguous_region(%X,%i,%i,%X,%i)\n",ptr,offset,size,buf,actual_size);
	#endif
	// let's just make sure
	if (actual_size+1 > size)
		return actual_size;
	int prev_size = actual_size;
	if (ptr->contiguous.nextPtr != NULL)
	{
		ptr = LIST_ENTRY_PTR(&(ptr->contiguous.nextPtr),MEM_REGION,contiguous);
		if (ptr->size_allocated+actual_size < size)
			actual_size += ptr->size_allocated;
		// in this case, a memcpy can stand alone b/c we can be certai
		memcpy((buf+prev_size),ptr->ptr,actual_size-prev_size);
		actual_size = append_contiguous_region(fd,ptr,offset,size,buf,actual_size);	
	}
	else
	{
		// no contiguous region exists, so we will be forced to perform an i/o operation
		// the priority for this read will be high since this may stop a previous request
		access_img_region(fd,offset+actual_size,size-actual_size,buf+prev_size,LOW_PRIORITY);
	}
		printf("no contiguous region\n");
	
	return actual_size;
	// if the ptr has a contiguous region, append it to buf starting at actual_size	
}

/*
 * access_img_region_ptr returns a pointer to a region of memory
 */
u_int64_t access_img_region_ptr(unsigned int fd, u_int64_t offset, u_int64_t size, char **buf, unsigned char read_priority)
{
		u_int64_t actual_size =size;
	#ifdef USE_SLAB
		
		actual_size = slab_ptr(fd,buf,offset,size,1);
#else
#endif	
	return actual_size;
}



void *pf_lock_work()
{
	void *ptr;
	pthread_mutex_lock( &work_mutex );
	if (pfm_queue_head == NULL)
	{
		pthread_mutex_unlock( &work_mutex );
		return NULL;
	}
	// pops the top element from the queue and returns it
	PWORK_OBJECT chunk = LIST_ENTRY_PTR(pfm_queue_head,WORK_OBJECT,queue_list);
	// assign the associated mem region so the signaled thread can use it
	ptr = chunk->work_ptr;
	
	// remove the entry from the list to indicate it is finished
	
	
	return ptr;
}

void pf_free_work()
{
	pthread_mutex_unlock( &work_mutex );
}


/*
 * pf_pop_work pops work from the work queue
 */
void *pf_pop_work()
{
	void *ptr;
	pthread_mutex_lock( &work_mutex );
	if (pfm_queue_head == NULL)
	{
		pthread_mutex_unlock( &work_mutex );
		return NULL;
	}
	
	// pops the top element from the queue and returns it
	PWORK_OBJECT chunk = LIST_ENTRY_PTR(pfm_queue_head,WORK_OBJECT,queue_list);
	KernUnlinkListEntry(&pfm_queue_head,&(chunk->queue_list));
	pthread_mutex_unlock( &work_mutex );
	// assign the associated mem region so the signaled thread can use it
	ptr = chunk->work_ptr;
	
	// remove the entry from the list to indicate it is finished
	
	
	free( chunk );
	
	return ptr;
	
}


/*
 * pf_push_work pushes work onto a work queue
 * 
 */
void pf_push_work(void *ptr)
{
	// accepts as input the pointer to add to the work_queue
	// queue the new chunk to be searched
	pthread_mutex_lock( &work_mutex );
	PWORK_OBJECT newptr = malloc(sizeof(WORK_OBJECT));
	newptr->work_ptr = ptr;
	if (pfm_queue_head == NULL)
	{
		
		KernAddListEntry(&pfm_queue_head,&(newptr->queue_list));
		pfm_queue_tail = pfm_queue_head;
	}
	else
	{
		
		KernAddListEntryAfter(&pfm_queue_head,pfm_queue_tail,&(newptr->queue_list));
		pfm_queue_tail = pfm_queue_tail->nextPtr;
	}
	pthread_mutex_unlock( &work_mutex );

}

/*
 * access_img_region is the default method for accessing a given region of memory
 */
u_int64_t access_img_region(unsigned int fd, u_int64_t offset, u_int64_t size, char *buf, unsigned char read_priority)
{
	#ifdef DEBUG
		printf(" -- access_img_region(%i,%i,%i,%X,%i)\n",fd,offset,size,buf,read_priority);
	#endif
	
		u_int64_t actual_size=size;
#ifdef USE_SLAB
		
		actual_size = access_slab(fd,buf,offset,size);
#else
	// returns link to previous first region
	PMEMREGION ptr;
	
	if (( ptr = within_previous_region(fd,offset,size) ) == NULL)
	{
		
		// need to get information from fd
		#ifdef DEBUG
			printf("performing read on device\n");
		#endif
		ptr = allocate_img_region(fd,offset,size,read_priority);
		if (ptr->size_allocated <= 0)
		{
			return -1;
		}
		if (ptr->size_allocated < size)
			actual_size = ptr->size_allocated;
				
		// in this case, a memcpy can stand alone b/c we can be certai
		memcpy(buf,ptr->ptr,actual_size);
		// pleasuring doing business with you	
		
		
	}
	else
	{
		/* don't need to do any file I/O
		 * ptr contains a memory region where we can find the offset
		 * however, ptr->size may be smaller than size, so then we
		 * must allocate a contiguous entry after ptr, using read-ahead 
		 */  
		 #ifdef DEBUG
			printf("Using previous read block\n");
		#endif
		u_int64_t differential = offset - ptr->offset;
		if (ptr->size_allocated <= size)
			actual_size = ptr->size_allocated - (offset-ptr->offset);
		else
			actual_size = size;
		
		// in this case, a memcpy can stand alone b/c we can be certai
		memcpy(buf,(ptr->ptr+(offset-ptr->offset)),actual_size); 
	}
	#ifdef DEBUG
			printf("Actual size %i, size %i, file size is %i offset is %i file offset is %i\n",actual_size,size, ptr->size_allocated,offset,ptr->offset);
		#endif
	
	if (actual_size < size)
	{
		actual_size = append_contiguous_region(fd,ptr,offset,size,buf,actual_size);
	}
#endif	
	return actual_size; 
	
	
}

/*
 *  free a given region of memory
 */
void free_region(unsigned int fd,u_int64_t offset)
{
	free_slab(fd,offset);
}
